#ifndef __SHM_HPP__
#define __SHM_HPP__

#include <iostream>
#include <unistd.h>
#include <cstring> //C++版本的string.h
#include <cerrno>  //C++版本的errno.h
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <fcntl.h>
#include <vector>
#include <ctime>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>

// #define
const int gCreator = 1;
const int gUser = 2;
const std::string gpathname = "/home/smark/111_Linux/code/lesson24/4.shm";
const int gproj_id = 0x66;
const int gShmSize = 4096;

class Shm
{
private:
    key_t getCommkey()
    {
        key_t k = ftok(_pathname.c_str(), _proj_id);
        if (k < 0)
        {
            perror("ftok fail\n");
        }
        return k;
    }

    int getShmid(key_t key, int size, int flag)
    {
        // 有了 IPC_CREAT | IPC_EXCL 这个选项之后，
        // 如果你要创建的共享内存不存在，创建之，如果存在，出错返回
        int shmid = shmget(key, size, flag);
        if (shmid < 0)
        {
            perror("shmget fail\n");
        }
        return shmid;
    }

    std::string roleToString(int who)
    {
        if (who == gCreator)
        {
            return "Creator";
        }
        else if (who == gUser)
        {
            return "User";
        }
        else
        {
            return "None";
        }
    }

    void *attachShm()
    {
        void *shmaddr = shmat(_shmid, nullptr, 0);
        if (shmaddr == nullptr)
        {
            perror("shmat");
        }
        std::cout << "who: " << _who << roleToString(_who) << " attach shm..." << std::endl;
        return shmaddr;
    }

    void detachShm(void *shmaddr)
    {
        if (_addrshm)
        {
            detachShm(_addrshm);
        }
        if (shmaddr == nullptr)
        {
            return;
        }
        shmdt(shmaddr);
        std::cout << "who: " << _who << roleToString(_who) << " detach shm..." << std::endl;
    }

public:
    Shm(const std::string &pathname, int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)
    {
        _key = getCommkey();
        if (_who == gCreator)
        {
            getShmForCreator();
        }
        else if (_who == gUser)
        {
            getShmForUser();
        }
        _addrshm = attachShm();
        std::cout << "shmid: " << _shmid << std::endl;
        std::cout << "key: " << ToHex(_key) << std::endl;
    }
    ~Shm()
    {
        if (_who == gCreator)
        {
            int res = shmctl(_shmid, IPC_RMID, nullptr);

            // if (_addrshm)
            // {
            //     detachShm(_addrshm);
            // }
        }
        std::cout << "shm remove done..." << std::endl;
    }

    std::string ToHex(key_t key)
    {
        char buffer[128];
        // snprintf可以把 k 这个整型变量转成十六进制的表示形式
        snprintf(buffer, sizeof(buffer), "0x%x", key);
        return buffer;
    }

    bool getShmForCreator()
    {
        if (_who == gCreator)
        {
            _shmid = getShmid(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
            if (_shmid >= 0)
            {
                std::cout << "shm create done..." << std::endl;
                return true;
            }
        }
        return false;
    }

    bool getShmForUser()
    {
        if (_who == gUser)
        {
            _shmid = getShmid(_key, gShmSize, IPC_CREAT | 0666);
            if (_shmid >= 0)
            {
                std::cout << "shm get done..." << std::endl;
                return true;
            }
        }
        return false;
    }

    void zero()
    {
        if (_addrshm)
        {
            memset(_addrshm, 0, gShmSize);
        }
        
    }

    void *addr()
    {
        return _addrshm;
    }

    void debugShm()
    {
        struct shmid_ds ds;
        int n = shmctl(_shmid, IPC_STAT, &ds);
        if (n < 0)
        {
            return;
        }
        std::cout << "ds.shm_perm.__key: " << ToHex(ds.shm_perm.__key) << std::endl;
        std::cout << "ds.shm_nattch: " << ds.shm_nattch << std::endl;
        
    }
private:
    key_t _key;
    int _shmid;

    // 由于要让两个不同的进程看到同一块内存空间，必须生成一个key值
    //  这里生成key值是一个通用的算法ftok()
    //  ftok()的参数有两个，第一个是真实的路径名，第二个是程序员自定义的项目号
    std::string _pathname;
    int _proj_id;

    int _who;
    void *_addrshm;
};

#endif
